Django & Dropzone js
You can also be interested in:
Hi everybody, this one is just an example of how you can integrate Dropzone.js in your Django application. It's just a basic example, but probably a good starting point.
My scenario is a sort of two steps form: I have a Sale model which can have many images associated.
class Sale(models.Model): title = models.CharField(_("titolo"), max_length=255) # ... class SaleImage(models.Model): sale = models.ForeignKey( Sale, verbose_name="vendita", related_name="images", on_delete=models.CASCADE ) image = models.ImageField(_("immagine"), upload_to=sale_image_file_name) # ...
The problem with Dropzone is that it tries to do everything automagically and that files are uploaded by ajax. That means that images are not uploaded when the user presses a submit button transmitting also other data (the ones we need to create the Sale object). Of course, we need a Sale instance before inserting images associated to it.
So I decided to split the form in two: first, the user inserts the Sale information, then after saving he can edit it and add images.
Another thing to manage is the presence of already uploaded images. I mean, this is an update view, so maybe we already have some images associated and I want them to be graphically displayed as the new inserted ones! For this reason, we'll customize a bit the Dropzone behaviour, attaching some callbacks to its managed events.
Let's start from the template.
{% block dropzone %} <div class="dropzone mt-4 mb-4" id="dropzone"> <div class="images-preview" id="dropzone-images-preview"> {% for image in object.images.all %} {% thumbnail image.image "120x120" crop="center" as im %} <div class="thumb" data-id="{{ image.id }}"> <i class="fa fa-remove"></i> <img class="img-thumbnail" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}"> </div> {% endthumbnail %} {% endfor %} </div> <div class="spinner" id="dropzone-spinner"><i class="fa fa-spinner fa-spin"></i></div> </div> <script charset="utf-8"> (function ($) { var thumbs = []; var csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value; Dropzone.autoDiscover = false; var deleteImage = function () { var thumb = $(this).parent('.thumb'); var id = thumb.attr('data-id'); $.get('{% url 'market:update-sale-images' object.id %}?image_id=' + id, function (data) { if (data.status) { thumb.remove(); } }) } $('#dropzone-images-preview .thumb i').on('click', deleteImage); var myDropzone = new Dropzone( "div#dropzone", { url: "{% url 'market:update-sale-images' object.id %}", params: {'csrfmiddlewaretoken': csrftoken}, acceptedFiles: 'image/*', addedfile: function (file) { $('#dropzone-spinner').addClass('active'); }, }, complete: function () { $('#dropzone-spinner').removeClass('active'); }, success: function (klass, data) { thumbs.forEach(function (thumb) { $('#dropzone-images-preview').append($('<div />', { class: 'thumb' }) .attr('data-id', data.id) .append( $('<img />', { class: 'img-thumbnail', src: thumb}), $('<i />', { class: 'fa fa-remove' }).on('click', deleteImage) )) }) thumbs = [] }, thumbnail: function (klass, dataUrl) { thumbs.push(dataUrl); return null; } } ); })(jQuery) </script> {% endblock dropzone %}
Some considerations:
- Lines 2-14: this is the dropzone area. I've added an images preview div including all already associated images (thumbs with remove icon), and a spinner I'll use as undetermined progress feedback while uploading an image (I'll disable the default Dropzone behaviour because I need to manage images preview myself). There is room for improvements here: you can implement your custom progress bar to show upload progress.
- Lines 17-19: I create a thumbs array that will keep all updates images until they're not shown as preview images. I read the CSRF token (which is present in another part of the template) and I tell Dropzone to avoid auto discovering feature.
- Line 21-29: this is the dele image function, which performs an ajax request and removes the image thumb on success image deletion
- Line 31: I attach the previous function to the remove icon click events.
- Lines 33-63: my Dropzone configuration. When a file is added I activate the spinner overlay, which will be deactivated on complete. When the thumbnail is ready (L58) I push the dataUrl into the thumbs array. When the upload is successful I create a thumb for each item in the thumbs array and then I clean it. I also attach the deleteImage callback to the newly generated thumb.
Now let's see a bit of SCSS:
#dropzone { align-items: center; border: 4px dashed #eee !important; display: flex; flex-direction: column; justify-content: center; position: relative; .dz-message { margin: 2rem 0 0; } } .spinner { align-items: center; background: rgba(255, 255, 255, .8); bottom: 0; display: none; color: #999; flex-direction: row; font-size: 2rem; left: 0; justify-content: center; position: absolute; right: 0; top: 0; &.active { display: flex; padding: 1rem 0; } } .images-preview { align-items: center; display: flex; flex-direction: row; justify-content: center; .thumb { margin: 0 .5rem; position: relative; img { border-radius: 20px; } .fa { align-items: center; background: #000; border-radius: 50%; color: #fff; cursor: pointer; display: flex; height: 25px; justify-content: center; opacity: .3; position: absolute; right: 15px; top: 15px; width: 25px; &:hover { opacity: 1; } } } }
And finally the django view:
class SaleImagesUpdateView(View): # GET to delete an image def get(self, request, pk): try: id = request.GET.get('image_id') sale_image = get_object_or_404(SaleImage, id=id) sale_image.delete() data = {'status': True} return JsonResponse(data) except: data = {'status': False} return JsonResponse(data) # POST to add an image def post(self, request, pk): sale = get_object_or_404(Sale, pk=pk) try: files = request.FILES.getlist('file') for filename in files: save_image = SaleImage(sale=sale, image=filename) save_image.save() data = {'status': True, 'id': save_image.id} return JsonResponse(data) except KeyError: pass data = {'status': False} return JsonResponse(data)
That's all, bye!
Your Smartwatch Loves Tasker!
Your Smartwatch Loves Tasker!
Featured
Archive
- 2021
- 2020
- 2019
- 2018
- 2017
- Nov
- Oct
- Aug
- Jun
- Mar
- Feb
- 2016
- Oct
- Jun
- May
- Apr
- Mar
- Feb
- Jan
- 2015
- Nov
- Oct
- Aug
- Apr
- Mar
- Feb
- Jan
- 2014
- Sep
- Jul
- May
- Apr
- Mar
- Feb
- Jan
- 2013
- Nov
- Oct
- Sep
- Aug
- Jul
- Jun
- May
- Apr
- Mar
- Feb
- Jan
- 2012
- Dec
- Nov
- Oct
- Aug
- Jul
- Jun
- May
- Apr
- Jan
- 2011
- Dec
- Nov
- Oct
- Sep
- Aug
- Jul
- Jun
- May